import Cesium, { EventHelper } from '../../../Ces/Cesium';
import ZoomNavigationControl from './ZoomNavigationControl.js';
import ResetViewNavigationControl from './ResetViewNavigationControl.js';
import loadView from '../Core/loadView.js';
import Utils from '../Core/Utils.js';
import svgCompassOuterRing from '../SvgPaths/svgCompassOuterRing.js';
import svgCompassGyro from '../SvgPaths/svgCompassGyro.js';
import svgCompassRotationMarker from '../SvgPaths/svgCompassRotationMarker.js';

let vectorScratch;
let oldTransformScratch;
let newTransformScratch;
let centerScratch;

class NavigationViewModel {
  // 核心数据
  terria: any;
  eventHelper: EventHelper;

  // 配置选项
  enableZoomControls: boolean;
  enableCompass: boolean;
  navigationLocked: boolean;

  // 控制组件
  controls: any[];

  // SVG 路径
  svgCompassOuterRing: any;
  svgCompassGyro: any;
  svgCompassRotationMarker: any;

  // 显示状态
  showCompass: boolean;
  heading: number;

  // 轨道状态
  isOrbiting: boolean;
  orbitCursorAngle: number;
  orbitCursorOpacity: number;
  orbitLastTimestamp: number;
  orbitFrame: any;
  orbitIsLook: boolean;
  orbitMouseMoveFunction: ((e: MouseEvent) => void) | undefined;
  orbitMouseUpFunction: ((e: MouseEvent) => void) | undefined;

  // 旋转状态
  isRotating: boolean;
  rotateInitialCursorAngle: number | undefined;
  rotateFrame: any;
  rotateIsLook: boolean;
  rotateMouseMoveFunction: ((e: MouseEvent) => void) | undefined;
  rotateMouseUpFunction: ((e: MouseEvent) => void) | undefined;

  // 事件订阅
  _unsubcribeFromPostRender: (() => void) | undefined;

  orbitTickFunction
  rotateInitialCameraAngle

  constructor(options) {
    vectorScratch = new Cesium.Cartesian2();
    oldTransformScratch = new Cesium.Matrix4();
    newTransformScratch = new Cesium.Matrix4();
    centerScratch = new Cesium.Cartesian3();

    this.terria = options.terria;
    this.eventHelper = new Cesium.EventHelper();
    this.enableZoomControls = (Cesium.defined(options.enableZoomControls)) ? options.enableZoomControls : true;
    this.enableCompass = (Cesium.defined(options.enableCompass)) ? options.enableCompass : true;
    this.navigationLocked = false;

    this.controls = options.controls;
    if (!Cesium.defined(this.controls)) {
      this.controls = [
        new ZoomNavigationControl(this.terria, true),
        new ResetViewNavigationControl(this.terria),
        new ZoomNavigationControl(this.terria, false)
      ];
    }

    this.svgCompassOuterRing = svgCompassOuterRing;
    this.svgCompassGyro = svgCompassGyro;
    this.svgCompassRotationMarker = svgCompassRotationMarker;

    this.showCompass = Cesium.defined(this.terria) && this.enableCompass;
    this.heading = this.showCompass ? this.terria.scene.camera.heading : 0.0;

    this.isOrbiting = false;
    this.orbitCursorAngle = 0;
    this.orbitCursorOpacity = 0.0;
    this.orbitLastTimestamp = 0;
    this.orbitFrame = undefined;
    this.orbitIsLook = false;
    this.orbitMouseMoveFunction = undefined;
    this.orbitMouseUpFunction = undefined;

    this.isRotating = false;
    this.rotateInitialCursorAngle = undefined;
    this.rotateFrame = undefined;
    this.rotateIsLook = false;
    this.rotateMouseMoveFunction = undefined;
    this.rotateMouseUpFunction = undefined;

    this._unsubcribeFromPostRender = undefined;

    Cesium.knockout.track(this, ['controls', 'showCompass', 'heading', 'isOrbiting', 'orbitCursorAngle', 'isRotating']);

    const that = this;

    function widgetChange() {
      if (Cesium.defined(that.terria)) {
        if (that._unsubcribeFromPostRender) {
          that._unsubcribeFromPostRender();
          that._unsubcribeFromPostRender = undefined;
        }

        that.showCompass = true && that.enableCompass;

        that._unsubcribeFromPostRender = that.terria.scene.postRender.addEventListener(() => {
          that.heading = that.terria.scene.camera.heading;
        });
      } else {
        if (that._unsubcribeFromPostRender) {
          that._unsubcribeFromPostRender();
          that._unsubcribeFromPostRender = undefined;
        }
        that.showCompass = false;
      }
    }

    this.eventHelper.add(this.terria.afterWidgetChanged, widgetChange, this);
    widgetChange();
  }

  setNavigationLocked(locked) {
    this.navigationLocked = locked;
    if (this.controls && this.controls.length > 1) {
      this.controls[1].setNavigationLocked(this.navigationLocked);
    }
  }

  destroy() {
    this.eventHelper.removeAll();
  }

  show(container) {
    let testing;
    if (this.enableZoomControls && this.enableCompass) {
      testing = '<div class="compass"  data-bind="visible: showCompass, event: { mousedown: handleMouseDown, dblclick: handleDoubleClick }">' +
        '<div class="compass-outer-ring-background"></div>' +
        ' <div class="compass-rotation-marker" data-bind="visible: isOrbiting, style: { transform: \'rotate(-\' + orbitCursorAngle + \'rad)\', \'-webkit-transform\': \'rotate(-\' + orbitCursorAngle + \'rad)\', opacity: orbitCursorOpacity }, cesiumSvgPath: { path: svgCompassRotationMarker, width: 145, height: 145 }"></div>' +
        ' <div class="compass-outer-ring" data-bind="style: { transform: \'rotate(-\' + heading + \'rad)\', \'-webkit-transform\': \'rotate(-\' + heading + \'rad)\' }, cesiumSvgPath: { path: svgCompassOuterRing, width: 145, height: 145 }"></div>' +
        ' <div class="compass-gyro-background"></div>' +
        ' <div class="compass-gyro" data-bind="cesiumSvgPath: { path: svgCompassGyro, width: 145, height: 145 }, css: { \'compass-gyro-active\': isOrbiting }"></div>' +
        '</div>' +
        '<div class="navigation-controls">' +
        '<!-- ko foreach: controls -->' +
        '<div data-bind="click: activate, attr: { title: $data.name }, css: $root.isLastControl($data) ? \'navigation-control-last\' : \'navigation-control\' ">' +
        '   <!-- ko if: $data.hasText -->' +
        '   <div data-bind="text: $data.text, css: $data.isActive ?  \'navigation-control-icon-active \' + $data.cssClass : $data.cssClass"></div>' +
        '   <!-- /ko -->' +
        '  <!-- ko ifnot: $data.hasText -->' +
        '  <div data-bind="cesiumSvgPath: { path: $data.svgIcon, width: $data.svgWidth, height: $data.svgHeight }, css: $data.isActive ?  \'navigation-control-icon-active \' + $data.cssClass : $data.cssClass"></div>' +
        '  <!-- /ko -->' +
        ' </div>' +
        ' <!-- /ko -->' +
        '</div>';
    } else if (!this.enableZoomControls && this.enableCompass) {
      testing = '<div class="compass"  data-bind="visible: showCompass, event: { mousedown: handleMouseDown, dblclick: handleDoubleClick }">' +
        '<div class="compass-outer-ring-background"></div>' +
        ' <div class="compass-rotation-marker" data-bind="visible: isOrbiting, style: { transform: \'rotate(-\' + orbitCursorAngle + \'rad)\', \'-webkit-transform\': \'rotate(-\' + orbitCursorAngle + \'rad)\', opacity: orbitCursorOpacity }, cesiumSvgPath: { path: svgCompassRotationMarker, width: 145, height: 145 }"></div>' +
        ' <div class="compass-outer-ring" data-bind="style: { transform: \'rotate(-\' + heading + \'rad)\', \'-webkit-transform\': \'rotate(-\' + heading + \'rad)\' }, cesiumSvgPath: { path: svgCompassOuterRing, width: 145, height: 145 }"></div>' +
        ' <div class="compass-gyro-background"></div>' +
        ' <div class="compass-gyro" data-bind="cesiumSvgPath: { path: svgCompassGyro, width: 145, height: 145 }, css: { \'compass-gyro-active\': isOrbiting }"></div>' +
        '</div>' +
        '<div class="navigation-controls"  style="display: none;" >' +
        '<!-- ko foreach: controls -->' +
        '<div data-bind="click: activate, attr: { title: $data.name }, css: $root.isLastControl($data) ? \'navigation-control-last\' : \'navigation-control\' ">' +
        '   <!-- ko if: $data.hasText -->' +
        '   <div data-bind="text: $data.text, css: $data.isActive ?  \'navigation-control-icon-active \' + $data.cssClass : $data.cssClass"></div>' +
        '   <!-- /ko -->' +
        '  <!-- ko ifnot: $data.hasText -->' +
        '  <div data-bind="cesiumSvgPath: { path: $data.svgIcon, width: $data.svgWidth, height: $data.svgHeight }, css: $data.isActive ?  \'navigation-control-icon-active \' + $data.cssClass : $data.cssClass"></div>' +
        '  <!-- /ko -->' +
        ' </div>' +
        ' <!-- /ko -->' +
        '</div>';
    } else if (this.enableZoomControls && !this.enableCompass) {
      testing = '<div class="compass"  style="display: none;" data-bind="visible: showCompass, event: { mousedown: handleMouseDown, dblclick: handleDoubleClick }">' +
        '<div class="compass-outer-ring-background"></div>' +
        ' <div class="compass-rotation-marker" data-bind="visible: isOrbiting, style: { transform: \'rotate(-\' + orbitCursorAngle + \'rad)\', \'-webkit-transform\': \'rotate(-\' + orbitCursorAngle + \'rad)\', opacity: orbitCursorOpacity }, cesiumSvgPath: { path: svgCompassRotationMarker, width: 145, height: 145 }"></div>' +
        ' <div class="compass-outer-ring" data-bind="style: { transform: \'rotate(-\' + heading + \'rad)\', \'-webkit-transform\': \'rotate(-\' + heading + \'rad)\' }, cesiumSvgPath: { path: svgCompassOuterRing, width: 145, height: 145 }"></div>' +
        ' <div class="compass-gyro-background"></div>' +
        ' <div class="compass-gyro" data-bind="cesiumSvgPath: { path: svgCompassGyro, width: 145, height: 145 }, css: { \'compass-gyro-active\': isOrbiting }"></div>' +
        '</div>' +
        '<div class="navigation-controls"    >' +
        '<!-- ko foreach: controls -->' +
        '<div data-bind="click: activate, attr: { title: $data.name }, css: $root.isLastControl($data) ? \'navigation-control-last\' : \'navigation-control\' ">' +
        '   <!-- ko if: $data.hasText -->' +
        '   <div data-bind="text: $data.text, css: $data.isActive ?  \'navigation-control-icon-active \' + $data.cssClass : $data.cssClass"></div>' +
        '   <!-- /ko -->' +
        '  <!-- ko ifnot: $data.hasText -->' +
        '  <div data-bind="cesiumSvgPath: { path: $data.svgIcon, width: $data.svgWidth, height: $data.svgHeight }, css: $data.isActive ?  \'navigation-control-icon-active \' + $data.cssClass : $data.cssClass"></div>' +
        '  <!-- /ko -->' +
        ' </div>' +
        ' <!-- /ko -->' +
        '</div>';
    } else if (!this.enableZoomControls && !this.enableCompass) {
      testing = '<div class="compass"  style="display: none;" data-bind="visible: showCompass, event: { mousedown: handleMouseDown, dblclick: handleDoubleClick }">' +
        '<div class="compass-outer-ring-background"></div>' +
        ' <div class="compass-rotation-marker" data-bind="visible: isOrbiting, style: { transform: \'rotate(-\' + orbitCursorAngle + \'rad)\', \'-webkit-transform\': \'rotate(-\' + orbitCursorAngle + \'rad)\', opacity: orbitCursorOpacity }, cesiumSvgPath: { path: svgCompassRotationMarker, width: 145, height: 145 }"></div>' +
        ' <div class="compass-outer-ring" data-bind="style: { transform: \'rotate(-\' + heading + \'rad)\', \'-webkit-transform\': \'rotate(-\' + heading + \'rad)\' }, cesiumSvgPath: { path: svgCompassOuterRing, width: 145, height: 145 }"></div>' +
        ' <div class="compass-gyro-background"></div>' +
        ' <div class="compass-gyro" data-bind="cesiumSvgPath: { path: svgCompassGyro, width: 145, height: 145 }, css: { \'compass-gyro-active\': isOrbiting }"></div>' +
        '</div>' +
        '<div class="navigation-controls"   style="display: none;" >' +
        '<!-- ko foreach: controls -->' +
        '<div data-bind="click: activate, attr: { title: $data.name }, css: $root.isLastControl($data) ? \'navigation-control-last\' : \'navigation-control\' ">' +
        '   <!-- ko if: $data.hasText -->' +
        '   <div data-bind="text: $data.text, css: $data.isActive ?  \'navigation-control-icon-active \' + $data.cssClass : $data.cssClass"></div>' +
        '   <!-- /ko -->' +
        '  <!-- ko ifnot: $data.hasText -->' +
        '  <div data-bind="cesiumSvgPath: { path: $data.svgIcon, width: $data.svgWidth, height: $data.svgHeight }, css: $data.isActive ?  \'navigation-control-icon-active \' + $data.cssClass : $data.cssClass"></div>' +
        '  <!-- /ko -->' +
        ' </div>' +
        ' <!-- /ko -->' +
        '</div>';
    }
    loadView(testing, container, this);
  }

  add(control) {
    this.controls.push(control);
  }

  remove(control) {
    // this.controls.remove(control);
  }

  isLastControl(control) {
    return (control === this.controls[this.controls.length - 1]);
  }

  handleMouseDown(viewModel, e) {
    const scene = this.terria.scene;
    if (scene.mode === Cesium.SceneMode.MORPHING) {
      return true;
    }
    if (viewModel.navigationLocked) {
      return true;
    }

    const compassElement = e.currentTarget;
    const compassRectangle = e.currentTarget.getBoundingClientRect();
    const maxDistance = compassRectangle.width / 2.0;
    const center = new Cesium.Cartesian2((compassRectangle.right - compassRectangle.left) / 2.0, (compassRectangle.bottom - compassRectangle.top) / 2.0);
    const clickLocation = new Cesium.Cartesian2(e.clientX - compassRectangle.left, e.clientY - compassRectangle.top);
    const vector = Cesium.Cartesian2.subtract(clickLocation, center, vectorScratch);
    const distanceFromCenter = Cesium.Cartesian2.magnitude(vector);

    const distanceFraction = distanceFromCenter / maxDistance;

    const nominalTotalRadius = 145;
    const norminalGyroRadius = 50;

    if (distanceFraction < norminalGyroRadius / nominalTotalRadius) {
      this.orbit(compassElement, vector);
    } else if (distanceFraction < 1.0) {
      this.rotate(compassElement, vector);
    } else {
      return true;
    }
  }

  handleDoubleClick(viewModel, e) {
    const scene = viewModel.terria.scene;
    const camera = scene.camera;

    const sscc = scene.screenSpaceCameraController;

    if (scene.mode === Cesium.SceneMode.MORPHING || !sscc.enableInputs) {
      return true;
    }
    if (viewModel.navigationLocked) {
      return true;
    }
    if (scene.mode === Cesium.SceneMode.COLUMBUS_VIEW && !sscc.enableTranslate) {
      return;
    }
    if (scene.mode === Cesium.SceneMode.SCENE3D || scene.mode === Cesium.SceneMode.COLUMBUS_VIEW) {
      if (!sscc.enableLook) {
        return;
      }

      if (scene.mode === Cesium.SceneMode.SCENE3D) {
        if (!sscc.enableRotate) {
          return;
        }
      }
    }

    const center = Utils.getCameraFocus(viewModel.terria, true, centerScratch);

    if (!Cesium.defined(center)) {
      // Globe is barely visible, so reset to home view.
      this.controls[1].resetView();
      return;
    }

    const cameraPosition = scene.globe.ellipsoid.cartographicToCartesian(camera.positionCartographic, new Cesium.Cartesian3());

    const surfaceNormal = scene.globe.ellipsoid.geodeticSurfaceNormal(center);

    const focusBoundingSphere = new Cesium.BoundingSphere(center, 0);

    camera.flyToBoundingSphere(focusBoundingSphere, {
      offset: new Cesium.HeadingPitchRange(0,
        // do not use camera.pitch since the pitch at the center/target is required
        Cesium.Math.PI_OVER_TWO - Cesium.Cartesian3.angleBetween(
          surfaceNormal,
          camera.directionWC
        ),
        // distanceToBoundingSphere returns wrong values when in 2D or Columbus view so do not use
        // camera.distanceToBoundingSphere(focusBoundingSphere)
        // instead calculate distance manually
        Cesium.Cartesian3.distance(cameraPosition, center)
      ),
      duration: 1.5
    });
  }

  static create(options) {
    const result = new NavigationViewModel(options);
    result.show(options.container);
    return result;
  }

  orbit(compassElement, cursorVector) {
    const scene = this.terria.scene;
    const sscc = scene.screenSpaceCameraController;

    // do not orbit if it is disabled
    if (scene.mode === Cesium.SceneMode.MORPHING || !sscc.enableInputs) {
      return;
    }
    if (this.navigationLocked) {
      return true;
    }

    switch (scene.mode) {
      case Cesium.SceneMode.COLUMBUS_VIEW:
        if (sscc.enableLook) {
          break;
        }

        if (!sscc.enableTranslate || !sscc.enableTilt) {
          return;
        }
        break;
      case Cesium.SceneMode.SCENE3D:
        if (sscc.enableLook) {
          break;
        }

        if (!sscc.enableTilt || !sscc.enableRotate) {
          return;
        }
        break;
      case Cesium.SceneMode.SCENE2D:
        if (!sscc.enableTranslate) {
          return;
        }
        break;
    }

    // Remove existing event handlers, if any.
    document.removeEventListener('mousemove', this.orbitMouseMoveFunction, false);
    document.removeEventListener('mouseup', this.orbitMouseUpFunction, false);

    if (Cesium.defined(this.orbitTickFunction)) {
      this.terria.clock.onTick.removeEventListener(this.orbitTickFunction);
    }

    this.orbitMouseMoveFunction = undefined;
    this.orbitMouseUpFunction = undefined;
    this.orbitTickFunction = undefined;

    this.isOrbiting = true;
    this.orbitLastTimestamp = Cesium.getTimestamp();

    const camera = scene.camera;

    if (Cesium.defined(this.terria.trackedEntity)) {
      // when tracking an entity simply use that reference frame
      this.orbitFrame = undefined;
      this.orbitIsLook = false;
    } else {
      const center = Utils.getCameraFocus(this.terria, true, centerScratch);

      if (!Cesium.defined(center)) {
        this.orbitFrame = Cesium.Transforms.eastNorthUpToFixedFrame(camera.positionWC, scene.globe.ellipsoid, newTransformScratch);
        this.orbitIsLook = true;
      } else {
        this.orbitFrame = Cesium.Transforms.eastNorthUpToFixedFrame(center, scene.globe.ellipsoid, newTransformScratch);
        this.orbitIsLook = false;
      }
    }

    this.orbitTickFunction = (e) => {
      const timestamp = Cesium.getTimestamp();
      const deltaT = timestamp - this.orbitLastTimestamp;
      const rate = (this.orbitCursorOpacity - 0.5) * 2.5 / 1000;
      const distance = deltaT * rate;

      const angle = this.orbitCursorAngle + Cesium.Math.PI_OVER_TWO;
      const x = Math.cos(angle) * distance;
      const y = Math.sin(angle) * distance;

      let oldTransform;

      if (this.navigationLocked) {
        return true;
      }

      if (Cesium.defined(this.orbitFrame)) {
        oldTransform = Cesium.Matrix4.clone(camera.transform, oldTransformScratch);
        camera.lookAtTransform(this.orbitFrame);
      }

      // do not look up/down or rotate in 2D mode
      if (scene.mode === Cesium.SceneMode.SCENE2D) {
        camera.move(new Cesium.Cartesian3(x, y, 0), Math.max(scene.canvas.clientWidth, scene.canvas.clientHeight) / 100 * camera.positionCartographic.height * distance);
      } else {
        if (this.orbitIsLook) {
          camera.look(Cesium.Cartesian3.UNIT_Z, -x);
          camera.look(camera.right, -y);
        } else {
          camera.rotateLeft(x);
          camera.rotateUp(y);
        }
      }

      if (Cesium.defined(this.orbitFrame)) {
        camera.lookAtTransform(oldTransform);
      }

      this.orbitLastTimestamp = timestamp;
    };

    const updateAngleAndOpacity = (vector, compassWidth) => {
      const angle = Math.atan2(-vector.y, vector.x);
      this.orbitCursorAngle = Cesium.Math.zeroToTwoPi(angle - Cesium.Math.PI_OVER_TWO);

      const distance = Cesium.Cartesian2.magnitude(vector);
      const maxDistance = compassWidth / 2.0;
      const distanceFraction = Math.min(distance / maxDistance, 1.0);
      const easedOpacity = 0.5 * distanceFraction * distanceFraction + 0.5;
      this.orbitCursorOpacity = easedOpacity;
    };

    this.orbitMouseMoveFunction = (e) => {
      const compassRectangle = compassElement.getBoundingClientRect();
      const center = new Cesium.Cartesian2((compassRectangle.right - compassRectangle.left) / 2.0, (compassRectangle.bottom - compassRectangle.top) / 2.0);
      const clickLocation = new Cesium.Cartesian2(e.clientX - compassRectangle.left, e.clientY - compassRectangle.top);
      const vector = Cesium.Cartesian2.subtract(clickLocation, center, vectorScratch);
      updateAngleAndOpacity(vector, compassRectangle.width);
    };

    this.orbitMouseUpFunction = (e) => {
      this.isOrbiting = false;
      document.removeEventListener('mousemove', this.orbitMouseMoveFunction, false);
      document.removeEventListener('mouseup', this.orbitMouseUpFunction, false);

      if (Cesium.defined(this.orbitTickFunction)) {
        this.terria.clock.onTick.removeEventListener(this.orbitTickFunction);
      }

      this.orbitMouseMoveFunction = undefined;
      this.orbitMouseUpFunction = undefined;
      this.orbitTickFunction = undefined;
    };

    document.addEventListener('mousemove', this.orbitMouseMoveFunction, false);
    document.addEventListener('mouseup', this.orbitMouseUpFunction, false);
    this.terria.clock.onTick.addEventListener(this.orbitTickFunction);

    updateAngleAndOpacity(cursorVector, compassElement.getBoundingClientRect().width);
  }

  rotate(compassElement, cursorVector) {
    const scene = this.terria.scene;
    const camera = scene.camera;

    const sscc = scene.screenSpaceCameraController;
    // do not rotate in 2D mode or if rotating is disabled
    if (scene.mode === Cesium.SceneMode.MORPHING || scene.mode === Cesium.SceneMode.SCENE2D || !sscc.enableInputs) {
      return;
    }
    if (this.navigationLocked) {
      return true;
    }

    if (!sscc.enableLook && (scene.mode === Cesium.SceneMode.COLUMBUS_VIEW || (scene.mode === Cesium.SceneMode.SCENE3D && !sscc.enableRotate))) {
      return;
    }

    // Remove existing event handlers, if any.
    document.removeEventListener('mousemove', this.rotateMouseMoveFunction, false);
    document.removeEventListener('mouseup', this.rotateMouseUpFunction, false);

    this.rotateMouseMoveFunction = undefined;
    this.rotateMouseUpFunction = undefined;

    this.isRotating = true;
    this.rotateInitialCursorAngle = Math.atan2(-cursorVector.y, cursorVector.x);

    if (Cesium.defined(this.terria.trackedEntity)) {
      // when tracking an entity simply use that reference frame
      this.rotateFrame = undefined;
      this.rotateIsLook = false;
    } else {
      const viewCenter = Utils.getCameraFocus(this.terria, true, centerScratch);

      if (!Cesium.defined(viewCenter) || (scene.mode === Cesium.SceneMode.COLUMBUS_VIEW && !sscc.enableLook && !sscc.enableTranslate)) {
        this.rotateFrame = Cesium.Transforms.eastNorthUpToFixedFrame(camera.positionWC, scene.globe.ellipsoid, newTransformScratch);
        this.rotateIsLook = true;
      } else {
        this.rotateFrame = Cesium.Transforms.eastNorthUpToFixedFrame(viewCenter, scene.globe.ellipsoid, newTransformScratch);
        this.rotateIsLook = false;
      }
    }

    let oldTransform;
    if (Cesium.defined(this.rotateFrame)) {
      oldTransform = Cesium.Matrix4.clone(camera.transform, oldTransformScratch);
      camera.lookAtTransform(this.rotateFrame);
    }

    this.rotateInitialCameraAngle = -camera.heading;

    if (Cesium.defined(this.rotateFrame)) {
      camera.lookAtTransform(oldTransform);
    }

    this.rotateMouseMoveFunction = (e) => {
      const compassRectangle = compassElement.getBoundingClientRect();
      const center = new Cesium.Cartesian2((compassRectangle.right - compassRectangle.left) / 2.0, (compassRectangle.bottom - compassRectangle.top) / 2.0);
      const clickLocation = new Cesium.Cartesian2(e.clientX - compassRectangle.left, e.clientY - compassRectangle.top);
      const vector = Cesium.Cartesian2.subtract(clickLocation, center, vectorScratch);
      const angle = Math.atan2(-vector.y, vector.x);

      const angleDifference = angle - this.rotateInitialCursorAngle;
      const newCameraAngle = Cesium.Math.zeroToTwoPi(this.rotateInitialCameraAngle - angleDifference);

      const camera = this.terria.scene.camera;

      let oldTransform;
      if (Cesium.defined(this.rotateFrame)) {
        oldTransform = Cesium.Matrix4.clone(camera.transform, oldTransformScratch);
        camera.lookAtTransform(this.rotateFrame);
      }

      const currentCameraAngle = -camera.heading;
      camera.rotateRight(newCameraAngle - currentCameraAngle);

      if (Cesium.defined(this.rotateFrame)) {
        camera.lookAtTransform(oldTransform);
      }
    };

    this.rotateMouseUpFunction = (e) => {
      this.isRotating = false;
      document.removeEventListener('mousemove', this.rotateMouseMoveFunction, false);
      document.removeEventListener('mouseup', this.rotateMouseUpFunction, false);

      this.rotateMouseMoveFunction = undefined;
      this.rotateMouseUpFunction = undefined;
    };

    document.addEventListener('mousemove', this.rotateMouseMoveFunction, false);
    document.addEventListener('mouseup', this.rotateMouseUpFunction, false);
  }
}

export default NavigationViewModel;
